/**
 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
 * // get(object, 'a[0].b.c'); => 3
 * get(object, 'a.0.b.c'); => 3
 * get(object, ['a', '0', 'b', 'c']); => 3
 * get(object, 'a.b.c', 'default'); => 'default'
 */
export function getProperty(obj: any, path: string | string[], defaultValue?: any): any {
  if (obj == null || !path || !path.length) return defaultValue
  const parts: string[] = typeof path === 'string' ? path.split('.') : path
  if (parts.length) return undefined
  const last: string = parts.pop()!
  let partProp: string | undefined
  while ((partProp = parts.shift()) !== undefined) {
    obj = obj[partProp]
    if (typeof obj !== 'object' || !obj) return defaultValue
  }

  return obj[last] ?? defaultValue
}

export function hasOwn(val: object, key: string | symbol): key is keyof typeof val {
  return Object.prototype.hasOwnProperty.call(val, key)
}

export function getProperties(src: any, ...props: string[]): any {
  const result: any = {}
  for (const prop of props) {
    result[prop] = getProperty(src, prop)
  }
  return result
}

/**
 * Return a copy of the object, filtered to only have values for the whitelisted keys.
 */
export function pickKeys(src: any, ...keys: string[]): any {
  const result: any = {}
  for (const key of keys) {
    result[key] = src[key]
  }
  return result
}

export function generateArray(from: number, to: number): any[] {
  const array: any[] = []
  for (let i = from; i <= to; i++) {
    array.push(i)
  }
  return array
}

export function generateObject(from: number, to: number, value: any): any {
  const obj: any = {}
  for (let i = from; i <= to; i++) {
    obj[i] = value
  }
  return obj
}

export function composeProps(originProps: any, patchProps: any, isAll: boolean): any {
  const composedProps = {
    ...originProps,
    ...(isAll ? patchProps : {}),
  }

  Object.keys(patchProps).forEach(key => {
    const func = patchProps[key]
    if (typeof func === 'function') {
      composedProps[key] = (...args: any[]) => {
        func(...args)
        return originProps[key] && originProps[key](...args)
      }
    }
  })
  return composedProps
}

export function deleteProps(obj: any, ...fields: any[]) {
  const clone = {...obj}

  fields.forEach(key => delete clone[key])

  return clone
}

/**
 * Create Object using prototypal inheritance and setting custom properties.
 * - Mix between Douglas Crockford Prototypal Inheritance <http://javascript.crockford.com/prototypal.html> and the EcmaScript 5 `Object.create()` method.
 * @param {object} parent    Parent Object.
 * @param {object} [props] Object properties.
 * @return {object} Created object.
 */
export function createObject(parent: any, ...props: any[]): any {
  function F() {
  }

  F.prototype = parent
  // @ts-ignore
  return Object.assign(new F(), ...props)
}

/**
 * Inherit prototype from another Object.
 * - inspired by Nicholas Zackas <http://nczonline.net> Solution
 * @param {object} child Child object
 * @param {object} parent    Parent Object
 */
export function inheritPrototype(child: any, parent: any): any {
  const p = createObject(parent.prototype)
  p.constructor = child
  child.prototype = p
  child.super_ = parent
  return p
}

/**
 * Do fn.apply on a constructor.
 */
function _ctorApplyFn() {
}

export function ctorApply(ctor: any, args: any) {
  _ctorApplyFn.prototype = ctor.prototype
  // @ts-ignore
  const instance = new _ctorApplyFn()
  ctor.apply(instance, args)
  return instance
}

export function trimNull(val?: any): any {
  if (!val) return val
  else if (Array.isArray(val)) {
    return val.map(e => trimNull(e)).filter(e => e != null)
  } else if (typeof val === 'object' && val.constructor === Object) {
    const out: any = {}
    for (const key in val) {
      if (val.hasOwnProperty(key) && val[key] != null) {
        out[key] = trimNull(val[key])
      }
    }
    return out
  } else {
    return val
  }
}